Skip to content

Add opencode2 support (dual-host plugin + sidebar status) - #3

Closed
prestonlogan wants to merge 1 commit into
moritzfl:mainfrom
prestonlogan:opencode2-support
Closed

prestonlogan wants to merge 1 commit into
moritzfl:mainfrom
prestonlogan:opencode2-support

Conversation

@prestonlogan

Copy link
Copy Markdown

Ports the plugin to opencode2 while keeping opencode 1.x support intact — no V1 behavior changes.

Dual-host design

  • Single package entry dual-exports V1 server() and V2 setup(); only src/index.ts touched (4-line additive edit).
  • The V1 pipeline (extraction → consolidation → injection → citation feedback) runs unchanged via a V1-client shim (src/v2/shim.ts).

V2 adaptations (docs/opencode2.md)

  • Registry-based session discovery (no list API); parentID backfill keeps excluding subagent children.
  • Extraction sessionless via generate.text; consolidation via helper sessions switched to the memorize agent.
  • Helpers are interrupted + released, never deleted (no V2 remove API); rows remain as inert codex-memory-* history.
  • No config API on V2, so unset extract_model/consolidation_model fall back to the session default — set them explicitly.
  • Both agents ship (memorize-extract hidden/unused, same as V1's skip-when-unused).

Sidebar status

  • New ./tui entry: Memory section in the session sidebar (activity, global scope, last success, Codex import) + /memory-status command, served by a read-only status RPC from the same snapshots as memory_inspect.
  • TUI constraints worth knowing: setup() must only claim slots (keymap.layer belongs in slot components), no <Show> with element children directly under <box>, and the TUI bundle may import only @opencode/plugin/tui + solid-js + @opentui/solid.

Verification

  • 37 new tests (tests/v2-*.test.ts); full suite 385 pass, typecheck, build, V1 smoke + contract, new contract:v2 — all green on this tree.
  • Live-verified against opencode2 beta-19296 service: tools, injection, discovery, citations, Codex import merge, sidebar render, and status dialog.

Single package entry dual-exports V1 server() and V2 setup(); the V1
pipeline runs unchanged via a V1-client shim (src/v2/). V2 adaptations:
registry-based discovery, sessionless extraction via generate.text,
helper sessions released (not deleted), explicit extract/consolidation
models, both agents ship (extract hidden).

Adds a Memory sidebar section and /memory-status via the ./tui entry
and a read-only status RPC. 37 v2 tests, contract:v2 check,
docs/opencode2.md.
@moritzfl

moritzfl commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Thanks for the thorough dual-host work — the shim-over-unchanged-pipeline approach, deny-first agents, and the honest docs/opencode2.md notes (no delete, registry discovery, citations staying in history) are the right shape if/when we do this.

I’m going to close this for now rather than merge it. That is not a reject of the approach. OpenCode 2 is still beta, with no public GA date, and I don’t want the PR sitting open indefinitely.

This plugin is tightly coupled to host surfaces we don’t have stable V2 equivalents for yet: session list/discovery, session delete, config, and a pre-persist citation hook. The adapter has to fill those gaps with a process-local session list, helper sessions that cannot be deleted, and fallback to the session’s default models. Citations also stay in saved history, because V2 has no hook to strip them before persist. That’s reasonable against a frozen API; it’s expensive against 0.0.0-beta-*.

On this tree it also isn’t V1-neutral, which is a merge blocker even aside from host churn:

  • @opencode/[email protected] is a hard dependency, and src/index.ts statically imports the V2 setup path, so every V1 install pulls the OpenCode2 SDK.
  • opencode.json is the V1 agent bundle (agent + public schema). A V2 agents map does not belong in that file.

I’d rather not ship that risk to existing 1.x users for a host that’s still moving.

Please reopen when you think the V2 plugin API is stable enough to target (non-beta SDK; list/delete or a documented helper-session lifecycle; config; persist/citation hook). That’s your call. At that point the bar is:

  • @opencode/plugin as an optional peer — V1 installs must not pull the V2 SDK
  • V2 only via ./v2 and ./tui — V1 "." stays { id, server }
  • no static V2 import from src/index.ts — V1 must not load src/v2/
  • no V2 keys in opencode.json — that file stays the V1 agent bundle

@moritzfl moritzfl closed this Sep 8, 2026
@moritzfl

Copy link
Copy Markdown
Owner

@prestonlogan I revisited this after #4, checked the V2 implementation, and tested a small server plugin against 2.0.3. I’d be happy to review an updated version. Please reopen this PR, or open a replacement if the original branch is no longer available.

Official non-beta @opencode/cli and @opencode/plugin releases now exist, so my earlier non-beta SDK requirement is met.

I also need to qualify my earlier API assessment. Session listing, deletion and configuration reads exist in the public HTTP client, but they are not exposed directly through the server-plugin context. The restricted SessionDomain and actual host implementation confirm that distinction.

There is a workable path, but the updated PR needs to address the following:

  1. Define the supported connection path. An isolated plugin test confirmed that Service.discover(), Service.headers() and the public client work against a registered local service. The plugin can verify that it found its own host by comparing the authenticated health response’s PID with its own nonzero process.pid. Plain serve does not publish that registration. I’m open to initially supporting the registered local service explicitly; other modes need a demonstrated connection strategy or a clear unsupported-capability diagnostic. The plugin must not call Service.ensure() and accidentally start or replace a server.

  2. Use complete discovery and transcripts on that supported path. The public session API provides listing, export and deletion. Discovery should remain global and paginated, with child/helper exclusion and restart recovery. For extraction, use full-history APIs rather than session.context: its implementation starts at the latest compaction boundary. No direct access to OpenCode’s database should be necessary.

  3. Preserve helper lifecycle and sandboxing. Session.remove interrupts and waits before deleting. In contrast, interrupt acknowledges cancellation before cleanup finishes, and wait ignores execution failure causes. Please preserve the shutdown/lease guarantees rather than treating either acknowledgement as successful consolidation. Agent registration is also location-scoped: the test confirmed that a project-local registration does not appear at the separate memory-root location. Global installation is one workable approach. Verify the agent exists there and that its effective permissions restrict consolidation to the memory workspace.

  4. Keep V1 isolated, but revise the entrypoint layout. My earlier ./v2-only requirement was too prescriptive. The V2 resolver prefers ./server, and the V1 resolver does too. A V2-only ./server would therefore be wrong. A lazy dual-host entrypoint or a separate V2 package can address this, but the installed-package tests must prove that V1 neither imports the V2 implementation nor requires its runtime dependencies. Keep V2 configuration out of the existing V1 agent bundle.

  5. Make model defaults and cancellation explicit. The plugin has catalog defaults; config.get returns configuration documents, not V1’s merged mutable config. Please document the extraction fallback and preserve explicit models and supported reasoning variants. Also, the current Promise adapter only forwards the input argument, so passing an AbortSignal as request options to a ctx method must not be assumed to cancel it. Cancellation needs a verified implementation.

  6. Separate citation accounting from citation stripping. session.text.ended provides completed text and stable identifiers, allowing accounting without waiting for another user turn. It still needs durable deduplication and reconciliation after missed events. However, the event is published after its durable commit, so this does not replace the pre-persist stripping hook. I’m open to accepting retained markup as a documented temporary limitation. Title exclusion is more straightforward now: normal context and title generation use separate hooks.

Please test the packed artifact on supported V1 versions and a pinned V2 release.

I’m no longer treating V2’s release status alone as a reason to keep this closed. The shared-pipeline approach still makes sense, provided its supported scope and remaining limitations are explicit. I also will point @jensenojs at this from #4 so the migration work is not duplicated.

Keep in mind that this is still early days for V2 Plugins and not everything I wrote is set in stone. I am happy to be proven otherwise if you find faults with my assessments.

@moritzfl moritzfl mentioned this pull request Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants